#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <assert.h>
#include <sys/time.h>
#include "2d5p_aceMesh.h"
#include "aceMesh_runtime.h"


double A[2][SIZEX][SIZEY];
double test[2][SIZEX][SIZEY];
#define alpha_d 0.0876
#define beta_d  0.0765
#define FREQ 1450000000
unsigned long slave_counts[64]={0};

unsigned long rpcc()
{
  unsigned long time;
  asm("rtc %0":"=r"(time):);
  return time;
}

#ifdef  _PROFILING
unsigned long slave_comp_count[64]={0};
unsigned long slave_dma_count[64]={0};
unsigned long slave_count[64]={0};
unsigned long total_slave_count=0;
unsigned long total_dma_count=0;
unsigned long total_comp_count=0;
unsigned long slave_up[64]={0};
unsigned long slave_up1[64]={0}; //only halo
unsigned long slave_down[64]={0};
unsigned long total_up=0;
unsigned long total_up1=0;
unsigned long total_down=0;
int reuse_num[64]={0};
#endif


extern void  slave_stencil_core(targs* args);
void my_neighbors(void** neighbor_addrs,int* num_neigh, targs* args)
{
  int starti = args->starti;
  int startj = args->startj;
  void* src_addr=args->src_addr;
  *num_neigh=0;
  if(starti >= BLKX)
     neighbor_addrs[(*num_neigh)++] = ((void*)((unsigned long)src_addr - BLKX * SIZEY * 8));
  if(starti < SIZEX - BLKX)
     neighbor_addrs[(*num_neigh)++] = ((void*)((unsigned long)src_addr + BLKX * SIZEY * 8));
  if(startj >= BLKY)
     neighbor_addrs[(*num_neigh)++] = ((void*)((unsigned long)src_addr - BLKY * 8));
  if(startj < SIZEY - BLKY)
     neighbor_addrs[(*num_neigh)++] = ((void*)((unsigned long)src_addr + BLKY * 8));
}

void stencil_core(targs* args)
{
   int i,j,k;
   int  dest = (args->iter) % 2 ; 
   int  src = 1 - dest;
   for(i =  args->starti; i <  args->endi; i++)
      for(j =  args->startj; j <  args->endj; j++)
         A[dest][i][j] = alpha_d * A[src][i][j]+\
                         beta_d * (A[src][i-1][j] + A[src][i+1][j] +\
                         A[src][i][j-1] + A[src][i][j+1]);
}


int Do2d5p(const int iter, const int num_threads)
{
  targs each;
  int i,j, itr; 
  for( itr = 0; itr < iter; ++itr)
  {
    acemesh_begin_split_task("3d7p iter"); 

 
     for(i = 1; i < SIZEX -1; i += BLKX)
     {   
       int endi = (i + BLKX > SIZEX - 1 ) ? SIZEX - 1 : i + BLKX;
       for(j = 1; j < SIZEY -1; j += BLKY)
       {   
          int endj = (j + BLKY > SIZEY -1 ) ? SIZEY - 1 : j + BLKY;
          each.starti = i;
          each.endi = endi;
          each.startj = j;
          each.endj = endj;
          each.iter = itr;
          each.src_addr=&A[(itr+1)%2][i][j];
          acemesh_push_wlist(1, &A[itr%2][i][j],NORMAL);
          acemesh_push_rlist(1, &A[(itr+1)%2][i][j], NORMAL);
#ifdef M2S
          if(itr%2==0)
          {
              acemesh_task_map_master();
              acemesh_task_generator_with_neighbors((TASK_FUNCPTR)stencil_core,
                                                (void*)(&each), sizeof(targs),
                                                 NULL, (NEIGHBOR_FUNCPTR)my_neighbors, &each);
          }else{
              acemesh_task_generator_with_neighbors((TASK_FUNCPTR)slave_stencil_core,
                                                (void*)(&each), sizeof(targs),
                                                 NULL, (NEIGHBOR_FUNCPTR)my_neighbors, &each);
          }
#elif S2M
          if(itr%2!=0)
          {
              acemesh_task_map_master();
              acemesh_task_generator_with_neighbors((TASK_FUNCPTR)stencil_core,
                                                (void*)(&each), sizeof(targs),
                                                 NULL, (NEIGHBOR_FUNCPTR)my_neighbors, &each);
          }else{
              acemesh_task_generator_with_neighbors((TASK_FUNCPTR)slave_stencil_core,
                                                (void*)(&each), sizeof(targs),
                                                 NULL, (NEIGHBOR_FUNCPTR)my_neighbors, &each);
          }
#else
          acemesh_task_generator_with_neighbors((TASK_FUNCPTR)slave_stencil_core,
                                                (void*)(&each), sizeof(targs),
                                                 NULL, (NEIGHBOR_FUNCPTR)my_neighbors, &each);
#endif
          acemesh_task_set_type(STENCIL_TASK);
       }      
     }
     acemesh_end_split_task();
#ifdef SPLIT
     if(itr%2!=0) acemesh_spawn_and_wait(1);
#endif
  }
  acemesh_spawn_and_wait(1);
  //acemesh_wait_for_all_task();
  return 0;
}


void init()
{
   int i,j;
   for(i = 0; i < SIZEX; i++)
     for(j = 0; j < SIZEY; j++)
     {    
        A[1][i][j] = (double)(i*2.5 + j*3.3) /3.0;
        A[0][i][j]=A[1][i][j];
     }
}

int check(const int iter)
{
   int i,j,k;
   int itr;
   int ii,jj,kk;
   printf("begin check\n");
    for(i = 0; i < SIZEX; i++)
     for( j = 0; j < SIZEY; j++)
     {
       test[1][i][j] = (double)(i*2.5 + j*3.3) /3.0; 
       test[0][i][j]=test[1][i][j];
     }
    for(itr = 0; itr < iter; ++itr)
     for(i = 1; i < SIZEX - 1; i++)
      for( j = 1; j < SIZEY - 1; j++)
      {
          test[itr%2][i][j] = alpha_d * test[(itr+1)%2][i][j]+\
                         beta_d * (test[(itr+1)%2][i-1][j] + test[(itr+1)%2][i+1][j] +\
                         test[(itr+1)%2][i][j-1] + test[(itr+1)%2][i][j+1]);
      }
    for(ii = 1; ii < SIZEX - 1; ii++)
      for(jj = 1; jj < SIZEY - 1; jj++)
          if( fabs(test[(iter+1)%2][ii][jj] - A[(iter+1)%2][ii][jj]) > 1e-4 ){
             printf("i: %d   j:  %d \n",ii,jj);    
             printf("test: %20.20f",test[(iter+1)%2][ii][jj]);
             printf("\nA: %20.20f",A[(iter+1)%2][ii][jj]);
             printf("\nno");               
             return -1;
         }
  printf("correct\n");
  return 0;
}

int main(int argc, char** argv)
{
    int ITER,num_threads;
    int i,j,k;
    unsigned long st,et;
    int total_reuse=0;
    int p=1;
    ITER = 100;
    num_threads = 1;
    if(argc > 1)
       num_threads = atoi(argv[1]);
    if(argc > 2)
       ITER = atoi(argv[2]);
    if(((SIZEX-2)%BLKX) != 0 || ((SIZEY-2)%BLKY) != 0)
    {
      printf("(SIZEX-2)%BLKX) and ((SIZEY-2)%BLKY) must == 0 \n");
      assert(0);
    }
    init();
    acemesh_runtime_init(num_threads);
    st=rpcc();
    //while(p);
    Do2d5p(ITER, num_threads);
    et=rpcc();
#ifdef _PROFILING
    j=0;
#ifdef _BLACKROW
    if(num_threads>PE_NCOL)
        j=PE_NCOL;
#endif
#if defined(_UNIFORM_C) || defined(_UNIFORM_R)
    if(num_threads>PE_NCOL)
        j=num_threads;
#endif
    printf("j=%d\n",j);
    for(i=0;i<num_threads+j;++i)
    {
      total_slave_count+=slave_count[i];
      total_dma_count+=slave_dma_count[i];
      total_comp_count+=slave_comp_count[i];
      total_up+=slave_up[i];
      total_up1+=slave_up1[i];
      total_down+=slave_down[i];
    //  total_reuse+=reuse_num[i];
    //  printf("%ld\n",slave_dma_count[i]);
    }
    printf("dma:");
    for(i=0;i<num_threads+j;i++)
    {
      printf(", %6.3f",(float)slave_dma_count[i]/FREQ);
      if(i%16==15) printf("\n");
    }
    printf("\n");
    printf("comp:");
    for(i=0;i<num_threads+j;i++)
    {
      printf(", %6.3f",(float)slave_comp_count[i]/FREQ);
      if(i%16==15) printf("\n");
    }
    printf("\n");
    printf("aver_slave_time=%6.3f\naver_dma_time=%6.3f\naver_comp_time=%6.3f\ntotal_reuse=%d\n",
             (float)total_slave_count/FREQ/num_threads,
             (float)total_dma_count/FREQ/num_threads,
             (float)total_comp_count/FREQ/num_threads,
             total_reuse);
    printf("elements up: %ld, halo_up %ld,down %ld\n",total_up,total_up1,total_down);
#endif
    printf("total_time:%4.2f\n",(float)(et-st)/FREQ);       
    acemesh_runtime_shutdown(); 
#ifdef CHECK
    if(!check(ITER))
        printf("check correct!\n");
    else 
    {
        printf("check_error\n");
        exit(1);
    }
#endif
    return 0;
}
